package info.opensoaring.server.impl;

import info.opensoaring.api.igc.Fix;
import info.opensoaring.api.igc.FlightLog;
import info.opensoaring.api.igc.FlightOptimization;
import info.opensoaring.client.rpc.FlightService;
import info.opensoaring.server.jdo.EncodedPath;
import info.opensoaring.server.jdo.Flight;
import info.opensoaring.util.igc.PolylineEncoder;
import info.opensoaring.util.igc.optimize.FlightOptimizer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;

import org.dozer.Mapper;

import com.google.appengine.api.users.UserService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
@Singleton
public class FlightServiceImpl extends RemoteServiceServlet implements FlightService {

    private Logger logger;

    private UserService userService;

    private PersistenceManagerFactory pmf;

    private Mapper mapper;

    private FlightOptimizer[] optimizers;

    @Inject
    public FlightServiceImpl(Logger logger, UserService userService, PersistenceManagerFactory pmf,
	    Mapper mapper, FlightOptimizer[] optimizers) {
	this.logger = logger;
	this.userService = userService;
	this.pmf = pmf;
	this.mapper = mapper;
	this.optimizers = optimizers;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<info.opensoaring.api.Flight> getFlights() {
	logger.info("Calling getFlights");

	PersistenceManager pm = pmf.getPersistenceManager();
	Query query = pm.newQuery(Flight.class);
	Collection<Flight> result = (Collection<Flight>) query.execute();

	Collection<info.opensoaring.api.Flight> flights = new ArrayList<info.opensoaring.api.Flight>();
	for (Flight flight : result) {
	    flights.add(mapper.map(flight, info.opensoaring.api.Flight.class));
	}
	return flights;
    }

    @Override
    public void addFlight(info.opensoaring.api.Flight flight) {
	logger.info("Adding Flight :: " + flight);

	Flight jdoFlight = mapper.map(flight, Flight.class);
	jdoFlight.setOwner(userService.getCurrentUser());

	String flightStr = "";
	try {
	    URL flightUrl = new URL("http://localhost:8080/test/test.igc");
	    BufferedReader flightBuff = new BufferedReader(new InputStreamReader(flightUrl.openStream()));
	    String line = "";
	    while ((line = flightBuff.readLine()) != null) {
		flightStr += line + "\n";
	    }
	    flightBuff.close();
	} catch (Exception e) {
	    
	}
	// FIXME: The above is just for testing, the real igc log data should come from the client
	FlightLog flightLog = new FlightLog(flightStr);
	flightLog.parse();
	logger.info("Flight Log :: " + flightLog.toString());

	PolylineEncoder polyEncoder = new PolylineEncoder();
	info.opensoaring.api.map.EncodedPath encodedPath = polyEncoder.encode(flightLog
		.getFlightFixes().toArray(new Fix[] {}));
	EncodedPath flightPath = mapper.map(encodedPath, EncodedPath.class);
	jdoFlight.setFlightPath(flightPath);
	logger.info("Encoded Path :: " + flightPath);
	
	jdoFlight.setOptimizations(optimizeFlight(flightLog));

	PersistenceManager pm = pmf.getPersistenceManager();
	pm.currentTransaction().begin();
	try {
	    pm.makePersistent(jdoFlight);
	    pm.currentTransaction().commit();
	} catch (Exception e) {
	    pm.currentTransaction().rollback();
	    pm.close();
	}
    }

    @Override
    public FlightLog analyseFlight(String url) {
	logger.info("Loading flight :: " + url);
	String line, flightStr = "";
	try {
	    URL flightUrl = new URL(url);
	    BufferedReader flightBuff = new BufferedReader(new InputStreamReader(flightUrl
		    .openStream()));

	    while ((line = flightBuff.readLine()) != null) {
		flightStr += line + "\n";
	    }
	} catch (MalformedURLException e) {
	    logger.warning(e.getMessage());
	} catch (IOException e) {
	    logger.warning(e.getMessage());
	}
	FlightLog flight = new FlightLog(flightStr);
	long time1 = System.currentTimeMillis();
	flight.validate();
	flight.parse();
	long time2 = System.currentTimeMillis();
	logger.info("Parsing / validation took " + (time2 - time1) / 1000.0 + " seconds");

	for (FlightOptimizer optimizer : optimizers) {
	    long start = System.currentTimeMillis();
	    FlightOptimization flightOpt = optimizer.optimize(flight);
	    long end = System.currentTimeMillis();
	    logger.info("Optimization using " + flightOpt.getOptimizerId() + " took "
		    + (end - start) / 1000.0 + " seconds");
	}

	// FIXME: Reenable fix retrieval (disabled for now as gwt client side
	// deserialization
	// is taking 15seconds plus for a long flight - 1200 fixes)
	flight.setFlightFixes(new ArrayList<Fix>());
	return flight;
    }

    private List<FlightOptimization> optimizeFlight(FlightLog flight) {
	List<FlightOptimization> optimizations = new ArrayList<FlightOptimization>();
	
	for (FlightOptimizer optimizer : optimizers) {
	    optimizations.add(optimizer.optimize(flight));
	}
	return optimizations;
    }
}
